查看原文
其他

会员服务优雅上下线实践

The following article is from 爱奇艺技术产品团队 Author 会员产品技术团队

随着会员业务的快速发展,会员系统架构也不断演进迭代,拆分出了多个微服务,提升了系统的稳定性和扩展能力。在敏捷的开发模式下,业务迭代更加快速,那么势必会经常发布线上服务,在服务上线的过程中,我们发现接口成功率会出现一定程度的下降,对于敏感业务直接影响了用户的体验。为了解决这个问题,我们对微服务上下线流程进行了优化,本文将详细介绍方案的设计和实现。


01

问题分析


  异常情况分析  

业务系统当前使用的是 Spring Boot 和 Spring Cloud 框架,服务发布流程如下图所示:



通过梳理服务流程发现,引起服务可用性降低、响应时间突增的原因有以下几点:

  • 过早销毁对象:服务正在处理请求,但此时对象被销毁导致请求报错。

  • 服务未及时下线:调用方不能及时感知服务已在下线中,仍会发送请求过来,但此时对象可能已经被销毁导致请求报错。

  • 过早注册服务:服务未初始化完成就被注册到了注册中心,导致接口响应时间突增甚至超时。


  优化方向  

基于上面的分析,可以通过以下方式解决相关的问题:

  1. 在上线过程中,当服务把依赖的资源都初始化完成后,才将实例注册到注册中心。

  2. 在下线过程中,服务调用方可以排除正在下线的实例,保证在一定的时间窗口内请求不会打到这个实例上。


02

解决方案


系统以集群方式提供服务,实例的上下线状态对业务无感知,由组件封装实例的状态转换,通过优雅上线、优雅下线组合来保证服务的无损发布。


  优雅上线  


通过预热功能实现资源初始化,预热模块是可插拔的,可全使用或者仅使用其中一个模块:

  1. 自定义预热:由业务方自行扩展实现预热逻辑。

  2. 线上请求回放预热:配置预热接口,拉取线上请求对本地服务预热,当接口调用达到配置的预热次数后,再将服务注册到注册中心。


  • 预热

在原生Spring Cloud Netflix基础上,定制开发了服务注册组件 GracefulServiceRegistration 并抽象了预热组件 WarmUp。在 Spring 容器初始化过程中,会扫描所有 WarmUp 实现类并注入到容器中,启动完成后由 GracefulServiceRegistration 组件调用 WarmUp 接口所有实现类的预热方法进行服务预热,预热完成之后再进行服务注册。


图例说明:

  • VClientAutoConfiguration:服务注册配置类,负责初始化GracefulServiceRegistration

  • GracefulServiceRegistration:服务注册类,触发服务预热逻辑执行

  • WarmUp:预热组件,由业务方自行扩展实现预热逻辑。框架默认实现:延迟5s(可配置)再执行服务注册、线上请求回放预热


  优雅下线  


优雅下线通过延迟下线和可靠负载功能组合实现,在下线过程中,服务实例需要先去取消注册并将自己标记为已下线,后续的接口请求都将获取到该实例的已下线标记。服务调用方根据下线标记把该实例从可用服务列表中剔除,保证在后续一定时间窗口内的请求都不会再打到这个实例上。具体交互流程见下图:



  • 延迟下线

上文提到在原生Spring Cloud Netflix基础上,定制开发了 GracefulServiceRegistration、WarmUp 等组件,在解决优雅下线问题时,我们又增加了调用插件 InvokePlugin。当 JVM 监听到 SIGTERM 信号时,下线钩子线程开始工作,先执行取消注册,然后通过 GracefulServiceRegistration 标记当前服务为下线中状态,并阻塞当前线程 5s(可配置)来保证当前正在处理的请求能够成功返回。如果此时收到调用方请求,InvokePlugin 会检查当前服务状态是否为下线中,如果是,直接返回下线标记。最后下线钩子线程被唤醒,再执行对象销毁逻辑。


图例说明:

  • VClientAutoConfiguration:服务注册配置类,负责初始化 InvokePlugin、GracefulServiceRegistration 等组件

  • GracefulServiceRegistration:服务注册类,负责延迟销毁对象、触发服务预热逻辑执行

  • InvokePlugin:请求调用插件类,负责执行请求时检查服务实例状态是否在下线中,如果在下线中,直接返回下线标记


  • 可靠负载

微服务框架使用Ribbon作为负载均衡策略,默认是轮询机制,BaseLoadBalancer 中维护了两个注册表集合:全量注册表 allServerList、可用注册表 upServerList,但是原生只使用了全量注册表,通过循环判断获取可用实例,这种方式可能会获取到不可用的实例,所以我们对逻辑进行了优化,新增一个路由规则,使用可用注册表保存可用服务实例,并增加任务剔除标记已下线的实例。

负载策略实现方式为,服务调用方接收到实例的下线标记时,将该实例加入失活队列,独立的任务线程处理失活队列,并维护可用注册表,且失活队列的另一个任务是在同步注册中心最新注册表的时候,不要把已排除的实例恢复到可用注册表中。通过重试 + 排除下线实例的方式,使业务得到更高的可用性。具体设计如下:



03

成果与总结


对接优雅上下线功能的服务在上线过程中,服务成功率可以提升到99.99%以上,有效解决了服务上线成功率的问题。对比数据见下图示例:



无优雅上下线(并行1台滚动上线)



开启优雅上下线(并行1台滚动上线)


—— 活动推荐——


参考阅读:



本文由高可用架构转载。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

       

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存